package com.liuya.safe.policy.service.impl;

import com.liuya.base.AbstractBaseService;
import com.liuya.base.BaseModel;
import com.liuya.common.exception.EntityExistException;
import com.liuya.common.IOUtil;
import com.liuya.common.MyHashMap;
import com.liuya.common.StringUtil;
import com.liuya.db.DBLevelException;
import com.liuya.db.sql.QueryFactory;
import com.liuya.safe.model.SafeUser;
import com.liuya.db.sql.xml.types.LinkerType;
import com.liuya.db.sql.xml.types.SimpleOperatorType;
import com.liuya.db.util.DBUtil;
import com.liuya.entitle.QueryTestResult;
import com.liuya.safe.util.Constants;
import com.liuya.safe.policy.service.QueryManagerService;
import com.liuya.safe.exception.AuthException;
import com.liuya.safe.model.SafeQuery;
import com.liuya.db.sql.xml.Sql;

import com.liuya.safe.model.SafeUserRole;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.sql.SQLException;
import java.util.*;

/**
 * 查询策略管理
 * @author liuya
 * @date 20160912
 */
@Service("queryManagerService")
public class QueryManagerServiceImpl extends AbstractBaseService<SafeQuery> implements QueryManagerService, Serializable{

    private static final String ROOT_ID = "0";
//    private static String storeDir = Constants.getQueryStoreDir();
    /** key/value=id<Integer>/query<com.liuya.entitle.Query> */
    private Map storeMap;
    //app名称，为支持同时为多套系统提供数据权限
    private String appName;
    private Comparator comp;
    private static Log log = LogFactory.getLog(QueryManagerServiceImpl.class);
    //默认存储文件名称，一般使用app名称，为支持同时为多套系统提供数据权限
    private static final String DEF_FILE_POSTFIX = "query";
    private String storeFilePostfix = DEF_FILE_POSTFIX;

    public QueryManagerServiceImpl(){
//        initQuery("");
    }

    public QueryManagerServiceImpl(String appName) {
        initQuery(appName);
    }

    private synchronized void initQuery(String appName){
        this.appName = appName;
        if(!StringUtil.isEmpty(appName))
            storeFilePostfix = "_" + appName + "_query.xml";
        else
            storeFilePostfix = "_query.xml";

        comp = new Comparator() {
            public int compare(Object o1, Object o2) {
                SafeQuery q1 = (SafeQuery) o1;
                SafeQuery q2 = (SafeQuery) o2;
                return q1.getName().compareTo(q2.getName());
            }
        };

        loadIntoMemory();
    }

    private synchronized void loadIntoMemory() {
        storeMap = new MyHashMap();
        Collection queries = mapper.selectAll(null);
        storeMap.clear();

        for (Iterator iter = queries.iterator(); iter.hasNext();) {
            SafeQuery query = (SafeQuery) iter.next();

            if (query.getIsLeaf()) {
                loadXmlContent(query);
            }
            storeMap.put(query.getId(), query);
        }

        // 构建tree
        // first, add a root node
        SafeQuery rootQuery = new SafeQuery();
        rootQuery.setId(ROOT_ID);
        rootQuery.setPid("-1");
        rootQuery.setName("root");
        rootQuery.setIsLeaf(false);
        storeMap.put(ROOT_ID, rootQuery);

        for (Iterator iter = queries.iterator(); iter.hasNext();) {
            SafeQuery query = (SafeQuery) iter.next();
            // 设置parent
            String pid = query.getPid();
            SafeQuery pNode = (SafeQuery) storeMap.get(pid);
            query.setParent(pNode);

            //设置child
            if (pNode != null) {
                Collection children = pNode.getChildren();
                if (children == null) {
                    children = new LinkedList();
                    pNode.setChildren(children);
                }
                children.add(query);
            }
        }
    }

    private void loadXmlContent(SafeQuery query) throws AuthException{
        // read xml content from io system
        String file = query.getFileName();
        if (!StringUtil.isEmpty(file)) {
            String storeFile = Constants.getQueryStoreDir() + file;

            try {
                String content = IOUtil.read(storeFile);
                query.setXmlContent(content);
            } catch (IOException e) {
                throw new AuthException(e);
            }
        }
//        System.out.println(query.getXmlContent());
        if (!StringUtil.isEmpty(query.getXmlContent())) {
            com.liuya.db.sql.xml.QueryType xmlQuery;
            try {
                xmlQuery = com.liuya.db.sql.xml.Query.unmarshal(new StringReader(query.getXmlContent()));
            } catch (Exception e) {
                throw new AuthException(e);
            }
            com.liuya.db.sql.Query sqlQuery = QueryFactory.getQuery(xmlQuery);
            query.setSqlQuery(sqlQuery);
        }
    }

    public Collection getLikelyQueries(String name) {
        List result = new LinkedList();
        for (Iterator iter = storeMap.values().iterator(); iter.hasNext();) {
            SafeQuery query = (SafeQuery) iter.next();
            String queryName = query.getName();
            if (queryName.indexOf(name) > -1) {
                result.add(query);
            }
        }
        return result;
    }

    public SafeQuery getQuery(String name, boolean isLeaf) {
        for (Iterator iter = storeMap.values().iterator(); iter.hasNext();) {
            SafeQuery query = (SafeQuery) iter.next();
            String queryName = query.getName();
            if (queryName.equals(name) && isLeaf == query.getIsLeaf()) {
                return query;
            }
        }
        return null;
    }

    public Collection checkSameNameQueries(String srcFile) {
        try {
            Sql sql = Sql.unmarshal(new FileReader(srcFile));
            return checkSameNameQueries(sql);
        } catch (Exception e) {
            throw new AuthException(e);
        }
    }

    private Collection checkSameNameQueries(Sql sql) {
        com.liuya.db.sql.xml.Query[] queries = sql.getQuery();
        Set toCheckNameSet = new HashSet();
        for (int i = 0; i < queries.length; i++) {
            String name = queries[i].getName();
            toCheckNameSet.add(name);
        }
        List sameNames = new LinkedList();
        for (Iterator iter = storeMap.values().iterator(); iter.hasNext();) {
            SafeQuery query = (SafeQuery) iter.next();
            String queryName = query.getName();
            if (query.getIsLeaf() && toCheckNameSet.contains(queryName)) {
                sameNames.add(queryName);
            }
        }
        return sameNames;
    }

    private String newQueryId() {
        try {
            long id = DBUtil.getSequenceNextVal("query_id").longValue();

            // we need id>0
            while (id <= 0) {
                id = DBUtil.getSequenceNextVal("query_id");
            }
            return String.valueOf(id);
        } catch (SQLException e) {
            throw new DBLevelException(e);
        }
    }

    /**
     * 删除xml头，像"<?xml version="1.0" encoding="UTF-8"?>"
     */
    private String removeXMLHead(String xml) {
        int index = xml.indexOf("?>");
        if (index > 0)
            return xml.substring(index + 2);
        else
            return xml;
    }

    public Collection getAllQueries() {
        if(storeMap==null || storeMap.size()<1)
            initQuery(appName);

        Collection values = storeMap.values();
        List result = new ArrayList(values.size());
        result.addAll(values);
        Collections.sort(result, comp);
        return result;
    }

    @Transactional
    public SafeQuery addQuery(String pid, String name, String description, boolean isLeaf) throws EntityExistException {
        if (isLeaf) {
            return addLeafQuery(pid, name, description);
        } else {
            return addBranchQuery(pid, name, description);
        }
    }

    private SafeQuery addLeafQuery(String pid, String name, String description) throws EntityExistException {
        SafeQuery query = new SafeQuery();
        query.setId(newQueryId());
        query.setName(name);
        query.setDescription(description);
        query.setInstallDate(new Date());
        query.setXmlContent(initXmlContent(name, description));
        query.setFileName(query.getId() + storeFilePostfix);
        query.setPid(pid);
        query.setIsLeaf(true);

        //将配置文件写到硬盘
//        String file = storeDir + query.getFileName();
        String file = Constants.getQueryStoreDir() + query.getFileName();
        try {
            IOUtil.write(file, query.getXmlContent());
        } catch (IOException e) {
            throw new AuthException(e);
        }

        mapper.insert(query);

        //更新缓存
        loadXmlContent(query);
        SafeQuery parent = (SafeQuery) storeMap.get(query.getPid());
        query.setParent(parent);
        parent.getChildren().add(query);
        storeMap.put(query.getId(), query);

        return query;
    }

    private String initXmlContent(String name, String description) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>          \r\n" + "<query name=\"" + name + "\" isRawSQL=\"false\" type=\"sql\">\r\n" + "    <select isDistinct=\"false\"/>                 \r\n" + "    <from/>                                        \r\n" + "    <where>                                        \r\n" + "        <expressionGroup linker=\"AND\"/>          \r\n" + "    </where>                                       \r\n" + "    <groupBy/>                                     \r\n" + "    <orderBy/>                                     \r\n" + "    <rawSQL>                                       \r\n" + "        <content></content>                        \r\n" + "        <select isDistinct=\"false\"/>             \r\n"
                + "    </rawSQL>                                      \r\n" + "    <storedProcedure>                              \r\n" + "        <content></content>                        \r\n" + "        <select isDistinct=\"false\"/>             \r\n" + "    </storedProcedure>                             \r\n" + "</query>";
    }

    public void deleteQuery(String id) {
        //检查缓存
        SafeQuery query = (SafeQuery) storeMap.get(id);
        if (query == null) {
            return;
        }
        if (query.getIsLeaf()) {
            deleteSingleQuery(id);
        } else {
            Collection children = query.getChildren();
            Iterator itr = children.iterator();
            String[] ids = new String[children.size()];
            for (int i = 0; i < ids.length; i++) {
                SafeQuery child = (SafeQuery) itr.next();
                ids[i] = child.getId();
            }
            for (int i = 0; i < ids.length; i++) {
                deleteQuery(ids[i]);
            }
            deleteSingleQuery(id);
        }
    }

    private void deleteSingleQuery(String id) {
        SafeQuery hint = new SafeQuery();
        hint.setId(id);
        mapper.deleteByPrimaryKey(hint.getId());
        //删除文件，由物理删除改为逻辑删除
        SafeQuery query = (SafeQuery) storeMap.get(id);
        //文件的删除改由管理员删除
//        if (query != null && !StringUtil.isEmpty(query.getFileName())) {
//            new File(Constants.getQueryStoreDir() + query.getFileName()).delete();
//        }

        //更新缓存
        SafeQuery parent = (SafeQuery) storeMap.get(query.getPid());
        query.setParent(null);
        query.setChildren(null);
        parent.getChildren().remove(query);
        storeMap.remove(query.getId());
    }

    public SafeQuery getQuery(String id) {
        if(storeMap==null || storeMap.get(id) == null) {
            initQuery("");
        }
        return (SafeQuery) storeMap.get(id);
    }

    public void updateQuery(String id, String name, String description) throws EntityExistException {
        SafeQuery original = getQuery(id);
        if (original == null) {
            return;
        }
        original.setName(name);
        original.setDescription(description);
        mapper.updateByPrimaryKey(original);

        String xmlContent = original.getXmlContent();
        if (StringUtil.isEmpty(xmlContent)) {
            //没有xml文件需要更新
            return;
        }
        com.liuya.db.sql.xml.QueryType xmlQuery;
        try {
            xmlQuery = com.liuya.db.sql.xml.Query.unmarshal(new StringReader(xmlContent));
        } catch (Exception e) {
            throw new AuthException(e);
        }
        xmlQuery.setName(name);

        updateQuery(id, (com.liuya.db.sql.xml.Query) xmlQuery);
    }

    /**
     * 更新策略
     * @param id
     * @param content
     * @throws EntityExistException
     */
    public void updateQuery(String id, com.liuya.db.sql.xml.Query content) throws EntityExistException {
        SafeQuery original = getQuery(id);
        if (original == null) {
            return;
        }
//        System.out.println("QueryManagerService UpdateQuery: " + content.toString());
        java.io.StringWriter xmlContentWriter = new java.io.StringWriter();
        try {
            content.marshal(xmlContentWriter);
        } catch (Exception e) {
            throw new AuthException(e);
        }
        original.setXmlContent(xmlContentWriter.toString());

        //更新xml配置文件
        String file = Constants.getQueryStoreDir() + original.getFileName();
        try {
            IOUtil.write(file, StringUtil.keepSpaceInContent(original.getXmlContent(), content.getRawSQL().getContent()));
        } catch (IOException e) {
            throw new AuthException(e);
        }

        //更新缓存
        loadXmlContent(original);
    }

    private SafeQuery addBranchQuery(String pid, String name, String description) throws DBLevelException, EntityExistException {
        SafeQuery query = new SafeQuery();
        query.setId(newQueryId());
        query.setName(name);
        query.setDescription(description);
        query.setInstallDate(new Date());
        query.setPid(pid);
        query.setIsLeaf(false);

        mapper.insert(query);

        //更新缓存
        SafeQuery parent = (SafeQuery) storeMap.get(query.getPid());
        query.setParent(parent);
        parent.getChildren().add(query);
        storeMap.put(query.getId(), query);

        return query;
    }

    public SafeQuery getQueryTree() {
        return getQuery(ROOT_ID);
    }

    public void moveQuery(String id, String newPid) {
        try {
            SafeQuery query = getQuery(id);
            SafeQuery oldParent = getQuery(query.getPid());
            SafeQuery newParent = getQuery(newPid);

            if (newParent.getIsLeaf()) {
                throw new AuthException("The target node may not be a leaf");
            }
            if (query == newParent || isCascadeChild(query.getId(), newParent.getId())) {
                throw new AuthException("This move will prodce a cycle.");
            }
            //更新节点
            query.setPid(newParent.getId());
            mapper.updateByPrimaryKey(query);

            //更新缓存
            query.setParent(newParent);
            oldParent.getChildren().remove(query);
            newParent.getChildren().add(query);
        } catch (AuthException e) {
            throw new AuthException("EntityExistException moveQuery");
        }
    }

    private boolean isCascadeChild(String pId, String id) {
        BaseModel parent = getQuery(pId);
        BaseModel child = getQuery(id);

        if (parent != null && child != null) {
            while (child.getParent() != null) {
                if (parent == child.getParent())
                    return true;
                else
                    child = child.getParent();
            }
        }
        return false;
    }

    @Transactional
    public SafeQuery copyQuery(String sourceId, String newName, String newDescription) throws EntityExistException {
        SafeQuery original = getQuery(sourceId);
        if (original == null) {
            throw new AuthException("The query to copy doesn't exist.");
        }
        if (!original.getIsLeaf()) {
            throw new AuthException("Cannot copy query group.");
        }
        SafeQuery query = new SafeQuery();
        query.setId(newQueryId());
        query.setName(newName);
        query.setDescription(newDescription);
        query.setInstallDate(new Date());
        query.setFileName(query.getId() + storeFilePostfix);
        query.setPid(original.getPid());
        query.setIsLeaf(original.getIsLeaf());

        if (query.getIsLeaf()) {
            //读取配置文件内容
            String file = original.getFileName();
            if (!StringUtil.isEmpty(file)) {
                String storeFile = Constants.getQueryStoreDir() + file;

                try {
                    String content = IOUtil.read(storeFile);
                    query.setXmlContent(content);
                } catch (IOException e) {
                    throw new AuthException(e);
                }
            }

            //更新属性名称，写到文件
            if (!StringUtil.isEmpty(query.getXmlContent())) {
                com.liuya.db.sql.xml.QueryType xmlQuery;
                try {
                    xmlQuery = com.liuya.db.sql.xml.Query.unmarshal(new StringReader(query.getXmlContent()));
                    xmlQuery.setName(query.getName());
                    java.io.StringWriter xmlContentWriter = new java.io.StringWriter();
                    try {
                        xmlQuery.marshal(xmlContentWriter);
                    } catch (Exception e) {
                        throw new AuthException(e);
                    }
                    query.setXmlContent(xmlContentWriter.toString());

                    //更新文件内容
                    file = Constants.getQueryStoreDir() + query.getFileName();
                    IOUtil.write(file, StringUtil.keepSpaceInContent(query.getXmlContent(), xmlQuery.getRawSQL().getContent()));
                } catch (Exception e) {
                    throw new AuthException(e);
                }
                com.liuya.db.sql.Query sqlQuery = QueryFactory.getQuery(xmlQuery);
                query.setSqlQuery(sqlQuery);
            }
        }
        mapper.insert(query);

        //更新缓存
        loadXmlContent(query);
        SafeQuery parent = (SafeQuery) storeMap.get(query.getPid());
        query.setParent(parent);
        parent.getChildren().add(query);
        storeMap.put(query.getId(), query);

        return query;
    }

    public void addReservedQuery(String userTypeName) {
        SafeQuery query = initReservedQuery(userTypeName);

        //写XML
        String file = Constants.getQueryStoreDir() + query.getFileName();
        try {
            IOUtil.write(file, query.getXmlContent());
        } catch (IOException e) {
            throw new AuthException(e);
        }

        try {
            mapper.insert(query);
        } catch (AuthException e) {
            throw new AuthException("EntityExistException addReservedQuery");
        }

        //更新缓存
        loadXmlContent(query);
        SafeQuery parent = (SafeQuery) storeMap.get(query.getPid());
        query.setParent(parent);
        parent.getChildren().add(query);
        storeMap.put(query.getId(), query);
    }

    private SafeQuery initReservedQuery(String userTypeName) {
        SafeQuery query = new SafeQuery();
        query.setId(String.valueOf(SafeQuery.RESERVED_QUERY_ID));
        query.setName(SafeQuery.RESERVED_QUERY_NAME);
        query.setDescription(SafeQuery.RESERVED_QUERY_DESCRIPTION);
        query.setInstallDate(new Date());
        query.setFileName(query.getId() + storeFilePostfix);
        query.setPid("0");
        query.setIsLeaf(true);
        //xml内容
        com.liuya.db.sql.xml.QueryType xmlQuery = initReservedXMLQuery(userTypeName);
        StringWriter sw = new StringWriter();
        try {
            xmlQuery.marshal(sw);
        } catch (Exception e) {
            throw new AuthException(e);
        }
        query.setXmlContent(sw.toString());

        return query;
    }

    private com.liuya.db.sql.xml.QueryType initReservedXMLQuery(String userTypeName) {
        String tableName = appName + "_" + userTypeName + "_userrole";
        String alias = "t1";
        StringReader reader = new StringReader(initXmlContent("", ""));
        com.liuya.db.sql.xml.QueryType query;
        try {
            query = com.liuya.db.sql.xml.Query.unmarshal(reader);
        } catch (MarshalException e) {
            log.error("", e);
            throw new AuthException(e);
        } catch (ValidationException e) {
            log.error("", e);
            throw new AuthException(e);
        }
        query.setName(SafeQuery.RESERVED_QUERY_NAME);
//        query.setDs(DBPower.getDefaultDsName());
        query.setIsRawSQL(false);
        // rawSQL
        com.liuya.db.sql.xml.RawSQL rawSQL = new com.liuya.db.sql.xml.RawSQL();
        rawSQL.setContent("");
        query.setRawSQL(rawSQL);

        // queryTypeSequence
        com.liuya.db.sql.xml.QueryTypeSequence queryTypeSequence = new com.liuya.db.sql.xml.QueryTypeSequence();
        query.setQueryTypeSequence(queryTypeSequence);

        // select
        com.liuya.db.sql.xml.Select select = new com.liuya.db.sql.xml.Select();
        query.getQueryTypeSequence().setSelect(select);
        select.setIsDistinct(false);
        select.setMappingClass(SafeUserRole.class.getName());
        // select-column
        com.liuya.db.sql.xml.Column roleIdColumn = new com.liuya.db.sql.xml.Column();
        roleIdColumn.setName("roleid");
        roleIdColumn.setTableAlias(alias);
        roleIdColumn.setJavaType("int");
        roleIdColumn.setProperty("roleId");
        select.addColumn(0, roleIdColumn);

        // from
        com.liuya.db.sql.xml.From from = new com.liuya.db.sql.xml.From();
        query.getQueryTypeSequence().setFrom(from);
        // from-table
        com.liuya.db.sql.xml.Table table = new com.liuya.db.sql.xml.Table();
        // special operate for SQL Server
        if (DBUtil.isSQLServer()) {
            table.setName("dbo." + tableName);
        } else {
            table.setName(tableName);
        }
        table.setAlias(alias);
        from.addTable(0, table);

        // where
        com.liuya.db.sql.xml.Where where = new com.liuya.db.sql.xml.Where();
        query.getQueryTypeSequence().setWhere(where);
        // where-expressionGroup
        com.liuya.db.sql.xml.ExpressionGroup expressionGroup = new com.liuya.db.sql.xml.ExpressionGroup();
        where.setExpressionGroup(expressionGroup);
        // expressionGroup-linker
        expressionGroup.setLinker(LinkerType.AND);
        // expressionGroup-binaryExpression
        com.liuya.db.sql.xml.BinaryExpression binaryExpression = new com.liuya.db.sql.xml.BinaryExpression();
        com.liuya.db.sql.xml.ExpressionGroupTypeItem item = new com.liuya.db.sql.xml.ExpressionGroupTypeItem();
        item.setBinaryExpression(binaryExpression);
        expressionGroup.addExpressionGroupTypeItem(0, item);
        // binaryExpression-operand1
        com.liuya.db.sql.xml.Operand1 operand1 = new com.liuya.db.sql.xml.Operand1();
        binaryExpression.setOperand1(operand1);
        // operand1-column
        com.liuya.db.sql.xml.Column userIdCcolumn = new com.liuya.db.sql.xml.Column();
        userIdCcolumn.setName("userid");
        userIdCcolumn.setTableAlias(alias);
        com.liuya.db.sql.xml.Operand operand = new com.liuya.db.sql.xml.Operand();
        operand.setColumn(userIdCcolumn);
        operand1.setOperand(operand);
        // binaryExpression-simpleOperator
        com.liuya.db.sql.xml.Operator simpleOperator = new com.liuya.db.sql.xml.Operator();
        simpleOperator.setSimpleOperator(SimpleOperatorType.valueOf("="));
        binaryExpression.setOperator(simpleOperator);
        // binaryExpression-operand2
        com.liuya.db.sql.xml.Operand2 operand2 = new com.liuya.db.sql.xml.Operand2();
        binaryExpression.setOperand2(operand2);
        // operand2-userValue
        com.liuya.db.sql.xml.UserValue userValue = new com.liuya.db.sql.xml.UserValue();
        userValue.setKey("id");
        operand = new com.liuya.db.sql.xml.Operand();
        com.liuya.db.sql.xml.Value value = new com.liuya.db.sql.xml.Value();
        value.setUserValue(userValue);
        operand.setValue(value);
        operand2.setOperand(operand);

        return query;
    }

    public QueryTestResult testQuery(com.liuya.db.sql.Query query, SafeUser user, Map context, int start, int limit) {
        return query.test(user, context, start, limit);
    }

    public SafeQuery cloneQuery(SafeQuery query) {
        SafeQuery newQuery = new SafeQuery();
        newQuery.setDescription(query.getDescription());
        newQuery.setFileName(query.getFileName());
        newQuery.setId(query.getId());
        newQuery.setInstallDate(query.getInstallDate());
        newQuery.setIsLeaf(query.getIsLeaf());
        newQuery.setName(query.getName());
        newQuery.setPid(query.getPid());
        loadXmlContent(newQuery);

        return newQuery;
    }

}
